// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2016-2019 The MagnaChain Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef MAGNACHAIN_MINER_H
#define MAGNACHAIN_MINER_H

#include "primitives/block.h"
#include "transaction/txmempool.h"

#include <stdint.h>
#include <memory>
#include "boost/multi_index_container.hpp"
#include "boost/multi_index/ordered_index.hpp"

extern uint64_t ReservePubContractBlockDataSize;
extern uint64_t ReserveCallContractBlockDataSize;
extern uint64_t ReserveBranchTxBlockDataSize;

class MCBlockIndex;
class MCChainParams;
class MCScript;
class MCKeyStore;

namespace Consensus { struct Params; };

static const bool DEFAULT_PRINTPRIORITY = false;

struct MCBlockTemplate
{
    MCBlock block;
    std::vector<MCAmount> vTxFees;
    std::vector<int64_t> vTxSigOpsCost;
    std::vector<unsigned char> vchCoinbaseCommitment;
};

// Container for tracking updates to ancestor feerate as we include (parent)
// transactions in a block
struct MCTxMemPoolModifiedEntry {
    MCTxMemPoolModifiedEntry(MCTxMemPool::txiter entry)
    {
        iter = entry;
        nSizeWithAncestors = entry->GetSizeWithAncestors();
        nModFeesWithAncestors = entry->GetModFeesWithAncestors();
        nSigOpCostWithAncestors = entry->GetSigOpCostWithAncestors();
    }

    MCTxMemPool::txiter iter;
    uint64_t nSizeWithAncestors;
    MCAmount nModFeesWithAncestors;
    int64_t nSigOpCostWithAncestors;
};

/** Comparator for MCTxMemPool::txiter objects.
 *  It simply compares the internal memory address of the MCTxMemPoolEntry object
 *  pointed to. This means it has no meaning, and is only useful for using them
 *  as key in other indexes.
 */
struct CompareCTxMemPoolIter {
    bool operator()(const MCTxMemPool::txiter& a, const MCTxMemPool::txiter& b) const
    {
        return &(*a) < &(*b);
    }
};

struct modifiedentry_iter {
    typedef MCTxMemPool::txiter result_type;
    result_type operator() (const MCTxMemPoolModifiedEntry &entry) const
    {
        return entry.iter;
    }
};

// This matches the calculation in CompareTxMemPoolEntryByAncestorFee,
// except operating on MCTxMemPoolModifiedEntry.
// TODO: refactor to avoid duplication of this logic.
struct CompareModifiedEntry {
    bool operator()(const MCTxMemPoolModifiedEntry &a, const MCTxMemPoolModifiedEntry &b) const
    {
        double f1 = (double)a.nModFeesWithAncestors * b.nSizeWithAncestors;
        double f2 = (double)b.nModFeesWithAncestors * a.nSizeWithAncestors;
        if (f1 == f2) {
            return MCTxMemPool::CompareIteratorByHash()(a.iter, b.iter);
        }
        return f1 > f2;
    }
};

// A comparator that sorts transactions based on number of ancestors.
// This is sufficient to sort an ancestor package in an order that is valid
// to appear in a block.
struct CompareTxIterByAncestorCount {
    bool operator()(const MCTxMemPool::txiter &a, const MCTxMemPool::txiter &b)
    {
        if (a->GetCountWithAncestors() != b->GetCountWithAncestors())
            return a->GetCountWithAncestors() < b->GetCountWithAncestors();
        return MCTxMemPool::CompareIteratorByHash()(a, b);
    }
};

typedef boost::multi_index_container<
    MCTxMemPoolModifiedEntry,
    boost::multi_index::indexed_by<
        boost::multi_index::ordered_unique<
            modifiedentry_iter,
            CompareCTxMemPoolIter
        >,
        // sorted by modified ancestor fee rate
        boost::multi_index::ordered_non_unique<
            // Reuse same tag from MCTxMemPool's similar index
            boost::multi_index::tag<ancestor_score>,
            boost::multi_index::identity<MCTxMemPoolModifiedEntry>,
            CompareModifiedEntry
        >
    >
> indexed_modified_transaction_set;

typedef indexed_modified_transaction_set::nth_index<0>::type::iterator modtxiter;
typedef indexed_modified_transaction_set::index<ancestor_score>::type::iterator modtxscoreiter;

struct update_for_parent_inclusion
{
    update_for_parent_inclusion(MCTxMemPool::txiter it) : iter(it) {}

    void operator() (MCTxMemPoolModifiedEntry &e) const
    {
        e.nModFeesWithAncestors -= iter->GetFee();
        e.nSizeWithAncestors -= iter->GetTxSize();
        e.nSigOpCostWithAncestors -= iter->GetSigOpCost();
    }

    MCTxMemPool::txiter iter;
};

class BranchUTXOCache
{
public:
    typedef std::map<MCOutPoint, MCTxOut> MAP_CACHE_COIN;

    CoinList coinlist;
    MAP_CACHE_COIN mapCacheCoin;
};

class MakeBranchTxUTXO
{
public:
    typedef std::map<uint160, BranchUTXOCache> MAP_BRANCH_COINS;
    typedef std::map<uint256, MCTransactionRef> MAP_MAKE_CACHE;
    
    MCAmount UseUTXO(const uint160& key, MCAmount nAmount, std::vector<MCOutPoint>& vInOutPoints);
    bool MakeTxUTXO(MCMutableTransaction& tx, const uint160& key, MCAmount nAmount, MCScript& scriptSig, MCScript& changeScriptPubKey);

    MAP_MAKE_CACHE mapCache;
    MAP_BRANCH_COINS mapBranchCoins;
};

/** Generate a new block, without valid proof-of-work */
class BlockAssembler
{
private:
    // The constructed block template
    std::unique_ptr<MCBlockTemplate> pblocktemplate;
    // A convenience pointer that always refers to the MCBlock in pblocktemplate
    MCBlock* pblock;

    // Configuration parameters for the block size
    bool fIncludeWitness;
    unsigned int nBlockMaxWeight;
    MCFeeRate blockMinFeeRate;

    // Information on the current status of the block
    uint64_t nBlockWeight;
    uint64_t nBlockTx;
    uint64_t nBlockSigOpsCost;
    MCAmount nFees;
    MCTxMemPool::setEntries inBlock;

    // Chain context for the block
    int nHeight;
    int64_t nLockTimeCutoff;
    const MCChainParams& chainparams;
	MCOutPoint outpoint;

public:
    struct Options {
        Options();
        size_t nBlockMaxWeight;
        size_t nBlockMaxSize;
        MCFeeRate blockMinFeeRate;
		MCOutPoint outpoint;
    };
	static Options DefaultOptions(const MCChainParams& params );

    BlockAssembler(const MCChainParams& params);
    BlockAssembler(const MCChainParams& params, const Options& options);

    /** Construct a new block template with coinbase to scriptPubKeyIn */
    std::unique_ptr<MCBlockTemplate> CreateNewBlock(const MCScript& scriptPubKeyIn, bool fMineWitnessTx, 
        const MCKeyStore* keystoreIn, MCCoinsViewCache *pcoinsCache, std::string& strErr);

private:
    // utility functions
    /** Clear the block's state and prepare for assembling a new block */
    void resetBlock();
    /** Add a tx to the block */
    void AddToBlock(MCTxMemPool::txiter iter, MakeBranchTxUTXO& utxoMaker);

    void GroupingTransaction(size_t offset, std::vector<const MCTxMemPoolEntry*>& blockTxEntries);

    // Methods for how to add transactions to a block.
    /** Add transactions based on feerate including unconfirmed ancestors
      * Increments nPackagesSelected / nDescendantsUpdated with corresponding
      * statistics from the package selection (for logging statistics). */
    void addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated);

    // helper functions for addPackageTxs()
    /** Remove confirmed (inBlock) entries from given set */
    void onlyUnconfirmed(MCTxMemPool::setEntries& testSet);

    uint64_t GetTxReserveBlockSize(const MCTransaction& tx);

    /** Test if a new package would "fit" in the block */
    bool TestPackage(MCTxMemPool::txiter iter, uint64_t packageSize, int64_t packageSigOpsCost);
    /** Perform checks on each transaction in a package:
      * locktime, premature-witness, serialized size (if necessary)
      * These checks should always succeed, and they're here
      * only as an extra check in case of suboptimal node configuration */
    bool TestPackageTransactions(const MCTxMemPool::setEntries& package);
    /** Return true if given transaction from mapTx has already been evaluated,
      * or if the transaction's cached data in mapTx is incorrect. */
    bool SkipMapTxEntry(MCTxMemPool::txiter it, indexed_modified_transaction_set &mapModifiedTx, MCTxMemPool::setEntries &failedTx);
    /** Sort the package in an order that is valid to appear in a block */
    void SortForBlock(const MCTxMemPool::setEntries& package, MCTxMemPool::txiter entry, std::vector<MCTxMemPool::txiter>& sortedEntries);
    /** Add descendants of given transactions to mapModifiedTx with ancestor
      * state updated assuming given transactions are inBlock. Returns number
      * of updated descendants. */
    int UpdatePackagesForAdded(const MCTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set &mapModifiedTx);

    //
    //void addReportProofTx(const MCTransactionRef &ptxReport, const MCScript &minerpkey, const MCCoinsViewCache* pCoinsCache);
    //void addReportProofTxs(const MCScript& scriptPubKeyIn, MCCoinsViewCache *pcoinsCache);
    bool UpdateIncompleteTx(MCTxMemPool::txiter iter, MakeBranchTxUTXO& utxoMaker);
};

/** Modify the extranonce in a block */
void IncrementExtraNonce(MCBlock* pblock, const MCBlockIndex* pindexPrev, unsigned int& nExtraNonce);
int64_t UpdateTime(MCBlockHeader* pblock, const Consensus::Params& consensusParams, const MCBlockIndex* pindexPrev);

#endif // MAGNACHAIN_MINER_H
